package cn.xiaolang.thread.threadlocal;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 父子线程传值(通过InheritableThreadLocal实现)
 * <p>
 * 虽然InheritableThreadLocal可以解决在子线程中获取父线程的值的问题，
 * 但是在使用线程池的情况下，由于不同的任务有可能是同一个线程处理，
 * 因此这些任务取到的值有可能并不是父线程设置的值
 * </p>
 *
 * @author Dean
 * @date 2021-01-20
 */
@Slf4j
public class ParentChildPassValueIssueSolve2Issue {

    private static final ThreadLocal<TargetUser> threadLocal = new InheritableThreadLocal<>();
    private static final ExecutorService executorService = Executors.newSingleThreadExecutor();


    /**
     * 执行第一个任务时，创建一个线程，执行初始化，将父线程的inheritableThreadLocals拷贝给子任务；
     * 调用get(InheritableThreadLocal 继承了ThreadLocal，重写了getMap方法，
     * 会返回给线程持有的inheritableThreadLocals变量；
     * <p>
     * 执行第二个任务时，由于使用的是同一个线程，因此调用get方法，返回的是这个线程持有的inheritableThreadLocals变量，
     * 而此时该变量中的value已被第一个任务改写，因此获取到并不是父线程原本的值
     * <p>
     * 虽然任务对value进行了重新赋值，但是并不影响父线程的值，因为value指向了一个新的地址。
     * 如果直接更改value,那么会影响父线程的值，因为指向的是同一个地址
     */
    @SuppressWarnings("uncheck")
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //主线程设置一个值
        threadLocal.set(new TargetUser("dean-parent"));
        executorService.submit(new ChildThread("task1")).get();
        executorService.submit(new ChildThread("task2")).get();

        log.info(" parent thread get result:{}", threadLocal.get());
        threadLocal.remove();
        executorService.shutdown();

    }

    static class ChildThread implements Runnable {

        private String taskName;

        public ChildThread(String taskName) {
            this.taskName = taskName;
        }

        @Override
        public void run() {

            //如果启用了线程（调用start方法），调用get方法是获取不到值的
            TargetUser originVal = threadLocal.get();
            log.info("[{}] get from parent directly:{}", taskName, originVal);

            // 修改从父线程获取到的值
            threadLocal.set(new TargetUser("value modify by[" + taskName + "]"));

        }
    }

    @Data
    @AllArgsConstructor
    private static class TargetUser {

        private String name;

    }

}


